import 'dart:convert';
import 'package:lib_database/lib_database.dart';
import 'package:lib_util/log/log.dart';
import 'package:lib_util/utils/utils.dart';
import 'package:lib_web3/signature_method.dart';
import 'package:web3dart/crypto.dart';
import 'package:web3dart/web3dart.dart';
import 'package:http/http.dart';
import 'package:flutter/services.dart';
import 'package:web3dart/credentials.dart';
import 'package:web3dart/contracts.dart';
import 'dart:core';
import 'package:hex/hex.dart';

class NFT721Info {
//  获取 NFT 的名称。
  String tokenId;
  String name;
  // 获取 余额
  String price;
  // 当前 持有人信息
  String owner;
  // 当前系列 地址
  String seriesAddress;
  // symbol
  String symbol;
  String? metadataURL;
  String? metadata;

  NFT721Info({
    required this.tokenId,
    required this.name,
    required this.price,
    required this.symbol,
    required this.owner,
    required this.seriesAddress,
    this.metadataURL = '',
    this.metadata = '',
  });
}

class FlutterWeb3 {
  var HexCharacters = '0123456789abcdef';

  static getERC721OwnerTokenIds(
      {required String rpcUrl, required String nftSeriesContract, required String userContract}) async {
    final ethClient = new Web3Client(rpcUrl, Client());
    try {
      final contractAddress = EthereumAddress.fromHex(nftSeriesContract);
      String erc721Abi = await rootBundle.loadString('assets/abi/erc721.abi.json');
      final contract = DeployedContract(
        ContractAbi.fromJson(erc721Abi, 'ownerOf'),
        contractAddress,
      );
      final result = await Future.wait([
        ethClient.call(
          contract: contract,
          function: contract.function('totalSupply'),
          params: [],
        ),
        ethClient.call(
          contract: contract,
          function: contract.function('balanceOf'),
          params: [EthereumAddress.fromHex(userContract)],
        ),
        // ethClient.call(
        //   contract: contract,
        //   function: contract.function('walletOfOwner'),
        //   params: [EthereumAddress.fromHex(userContract)],
        // )
      ]);
      int seriesNftCount = result[0].first != null ? result[0].first.toInt() : 0;
      int ownerNftCounts = result[1].first != null ? result[1].first.toInt() : 0;
      // 获取 用户实际拥有的nft 数量
      // print('用户持有总数量：  ${result[2].first}');
      List<String> ownerNftList = [];
      // 遍历所有NFT，查询每个NFT的系列名称并进行匹配
      for (var i = 0; i < seriesNftCount; i++) {
        final tokenId = BigInt.from(i);
        if (ownerNftList.length < ownerNftCounts) {
          final owner = await ethClient.call(
            contract: contract,
            function: contract.function('ownerOf'),
            params: [BigInt.parse(tokenId.toString())],
          );
          if (owner.first != null && owner.first.toString().toLowerCase() == userContract.toLowerCase()) {
            ownerNftList.add(tokenId.toString());
          }
        } else {
          break;
        }
      }
      await ethClient.dispose();
      return ownerNftList;
    } catch (e) {
      await ethClient.dispose();
      return [];
    }
  }

  // 查询用户某个合约的nft

  static Future<List<NFT721Info>?> getERC721NFTListInfo(
      {required String rpcUrl,
      required String nftSeriesContract,
      required String userContract,
      required List<String> userOwnerNftTokenIds}) async {
    final ethClient = new Web3Client(rpcUrl, Client());
    try {
      List<NFT721Info> NFT721InfoData = [];
      final contractAddress = EthereumAddress.fromHex(nftSeriesContract);
      String erc721Abi = await rootBundle.loadString('assets/abi/erc721.abi.json');
      final contract = DeployedContract(
        ContractAbi.fromJson(erc721Abi, 'ownerOf'),
        contractAddress,
      );
      // 获取 该系列的 地板价
      final nftSeriesResponse = await Future.wait([
        ethClient.call(
          contract: contract,
          function: contract.function('name'),
          params: [],
        ),
        // ethClient.call(
        //   contract: contract,
        //   function: contract.function('getPrice'),
        //   params: [],
        // ),
        ethClient.call(
          contract: contract,
          function: contract.function('symbol'),
          params: [],
        ),
        // 获取 该合约 总值
        // ethClient.call(
        //   contract: contract,
        //   function: contract.function('withdrawAll'),
        //   params: [],
        // ),
        // ethClient.call(
        //   contract: contract,
        //   function: contract.function('owner'),
        //   params: [],
        // ),
      ]);
      print('nftSeriesResponse $nftSeriesResponse');
      // 遍历所有NFT，查询每个NFT的系列名称并进行匹配
      for (var i = 0; i < userOwnerNftTokenIds.length; i++) {
        final tokenId = BigInt.parse(userOwnerNftTokenIds[i]);
        var item = NFT721Info(
          tokenId: userOwnerNftTokenIds[i],
          symbol: nftSeriesResponse[1].first ?? '',
          name: nftSeriesResponse[0].first ?? '',
          owner: userContract,
          price: '0',
          seriesAddress: nftSeriesContract,
        );

        // 获取 头像信息
        final metadataURL = await ethClient.call(
          contract: contract,
          function: contract.function('tokenURI'),
          params: [tokenId],
        );
        item.metadataURL = metadataURL.first;
        if (metadataURL.first != null) {
          var res = await Client().get(Uri.parse(metadataURL.first.toString()));
          item.metadata = res.body;
        }
        NFT721InfoData.add(item);
      }
      await ethClient.dispose();
      return NFT721InfoData;
    } catch (e) {
      await ethClient.dispose();
      return null;
    }
  }

  // 获取主币余额
  static Future<String> getMainTokenBalance(String rpcUrl, String userAddress, {int decimal = 18}) async {
    final ethClient = new Web3Client(rpcUrl, Client());
    try {
      // 1. 连接以太坊节点
      final EthereumAddress fromAddress = EthereumAddress.fromHex(userAddress);

      //使用Infura API连接地址

      // 3. 调用balanceOf方法查询账户余额
      EtherAmount balance = await ethClient.getBalance(fromAddress);
      final result = balance.getInWei.toString();
      await ethClient.dispose();
      if (result == '0') {
        return '0';
      }
      return Utils.formatStringBalance((BigInt.parse(result) / BigInt.from(10).pow(decimal)).toString(),
          fractionDigits: Config.balanceFixedNum);
    } catch (e) {
      await ethClient.dispose();
      return '0';
    }
  }

  // 获取 代币 余额
  static Future<String> getTokenBalance(String rpcUrl, String tokenAddress, String userAddress,
      {int decimals = 18}) async {
    final ethClient = new Web3Client(rpcUrl, Client());
    try {
      // 1. 连接以太坊节点
      final EthereumAddress address = EthereumAddress.fromHex(tokenAddress);
      final EthereumAddress fromAddress = EthereumAddress.fromHex(userAddress);
      //使用Infura API连接地址

      String erc20Abi = await rootBundle.loadString('assets/abi/erc20.abi.json');
      // 2. 通过合约地址创建ERC20合约实例
      final contract = new DeployedContract(new ContractAbi.fromJson(erc20Abi, 'Token'), address);
      // 3. 调用balanceOf方法查询账户余额
      final result =
          await ethClient.call(contract: contract, function: contract.function('balanceOf'), params: [fromAddress]);
      // return balance.first as BigInt;

      // BigInt result = BigInt.parse(Utils.ensureRemoveHexPrefix(balance.first), radix: 16);
      await ethClient.dispose();
      return Utils.formatStringBalance((result.first / BigInt.from(10).pow(decimals)).toString(),
          fractionDigits: Config.balanceFixedNum);
    } catch (e) {
      await ethClient.dispose();
      return '0';
    }
  }

  // 获取 以太区块高度
  static Future<int> getERC20BlockHeightByRpc(rpcUrl) async {
    final client = Web3Client(rpcUrl, Client());
    try {
      int blockNumber = await client.getBlockNumber();
      await client.dispose();
      return blockNumber;
    } catch (e) {
      await client.dispose();
      return 0;
    }
  }


  static Future<String?> ethSignData(String signData) async {
    try {
      if (signData.contains('0x')) {
        signData = signData.substring(2, signData.length);
      }
      final signDataMessage = HEX.decode(signData);
      final message = utf8.decode(signDataMessage);
      final messageLength = message.length.toString();
      final prefixedMessage = '\x19Ethereum Signed Message:\n$messageLength$message';
      final messageBytes = utf8.encode(prefixedMessage);
      final data = keccak256(messageBytes);
      final hashedMessageString = bytesToHex(data);
      return hashedMessageString;
    } catch (e) {
      print('ethSignData error $e');
      throw Exception(e);
    }
  }

  // 拿 MPC 签名后的 内容 进行 转账交易
  static Future<String> ethSignSendTransaction(String rpcUrl, int chainId, List<dynamic> txInfo, String signInfo,
      {bool status = false}) async {
    final web3 = Web3Client(rpcUrl, Client());
    try {
      var bs = SignatureMethod.splitSignature('0x' + signInfo);
      var v = 27 + bs['recoveryParam'];
      v += chainId * 2 + 8;
      txInfo[6] = SignatureMethod.hexlify(v);
      txInfo[7] = bs['r'];
      txInfo[8] = bs['s'];

      final rlpEnCodeTransaction = SignatureMethod.encode(txInfo);
      // print('111111111${hexlify(rlpEnCodeTransaction)}');
      // EthChain().Rlp(txInfo.toString()).then((data) async {
      //   print('11111111111$data');
      //   var transactionHash = await web3.sendRawTransaction(hexToBytes(data));
      //   print('11111111$transactionHash');
      // });
      var transactionHash = await web3.sendRawTransaction(hexToBytes(rlpEnCodeTransaction));
      // 监听交易哈希事件
      // web3.on(transactionHash).listen((receipt) {
      //   // 交易收据事件回调
      //   print('receipt: $receipt');
      //   // 在这里处理交易收据
      // }, onError: (error) {
      //   // 错误事件回调
      //   print('error: $error');
      //   // 在这里处理错误
      // });

      // 或者使用client.getTransactionReceipt方法获取交易收据
      if (status)
        localTranData(web3, transactionHash);
      else
        await web3.dispose();
      return transactionHash;
    } catch (e) {
      await web3.dispose();
      throw Exception(e);
    }
  }

  //交易成功后修改缓存数据
  static localTranData(web3, hash) async {
    try {
      var data = await web3.getTransactionReceipt(hash);
      if (data == null) {
        return await web3.dispose();
      }
      if (data is TransactionReceipt) {
        int gas = int.parse(data.gasUsed.toString());
        int gasPrice = int.parse(data.effectiveGasPrice!.getInWei.toString());
        String gasFee = Utils.formatStringBalance(
            (BigInt.parse((gas * gasPrice).toString()) / BigInt.from(10).pow(18)).toString(),
            fractionDigits: Config.gasFeeFixedNum,
            trimDecimalZero: true);
        int? status = (data.status != null && data.status!) ? 1 : 0;

        String block = data.blockNumber.toString();
        String txId = '0x' + bytesToHex(data.transactionHash);
        //receipt,上链了,并且链上执行成功
        ObjectBox().transactionModel.updatePendingTransactionState2(
            TransactionBean(txId: txId, height: block, txFee: gasFee, state: status.toString(), pending: false));
      }
    } catch (err) {
      await web3.dispose();
      Log.logger.e(err);
    }
  }

  // 以太 签名 MPC 转账 算法
  static Future<Map<String, dynamic>?> getWeb3SignTx(
      rpcUrl, int chainId, Map<String, dynamic> tran, tokenAddress, type) async {
    final web3 = Web3Client(rpcUrl, Client());
    try {
      var txData;
      if (tran['nonce'] == null || tran['nonce'] == '' || tran['nonce'] == 0) {
        final count = await web3.getTransactionCount(EthereumAddress.fromHex(tran['from']));
        tran['nonce'] = count;
      }
      if ((tran['data'] == null || tran['data'].toString().isEmpty) && tokenAddress.toString().isNotEmpty) {
        String erc20Abi = await rootBundle.loadString('assets/abi/erc20.abi.json');
        final contract = DeployedContract(
          ContractAbi.fromJson(erc20Abi, 'Contract'), // 请替换为您的合约ABI定义和合约名称
          EthereumAddress.fromHex(tokenAddress),
        );
        final transferMethods = contract.function('transfer');
        final encodedData = transferMethods.encodeCall(
          [EthereumAddress.fromHex(tran['to']), BigInt.parse(tran['value'])],
        );
        txData = encodedData;
        tran['value'] = '';
        tran['to'] = tokenAddress;
      } else if ((tran['data'] != null || tran['data'].toString().isNotEmpty) && tokenAddress.toString().isNotEmpty) {
        txData = tran['data'];
      } else {
        txData = '0x';
      }

      if (tran['gas'] == null || tran['gas'].toString().isEmpty) {
        var gasLimit = await web3.estimateGas(
          sender: EthereumAddress.fromHex(tran['from']),
          to: EthereumAddress.fromHex(tran['to']),
          data: hexToBytes(txData ?? ''),
          value: EtherAmount.fromUnitAndValue(EtherUnit.wei, BigInt.parse(tran['value'])),
        );
        tran['gas'] = gasLimit.toString();
      }
      if (tran['gasPrice'] == null && tran['gasPrice'].toString().isEmpty) {
        var gasPrice = await web3.getGasPrice();
        tran['gasPrice'] = gasPrice.getInWei.toString();
      }
      var tran2 = [
        tran['nonce'] == 0 ? '0x' : SignatureMethod.hexlify(tran['nonce']),
        SignatureMethod.hexlify(int.parse(tran['gasPrice'])),
        SignatureMethod.hexlify(int.parse(tran['gas'])),
        SignatureMethod.hexlify(tran['to']),
        SignatureMethod.hexlify(
            (tran['value'].toString().isEmpty || tran['value'] == null) ? '0x' : int.parse(tran['value'])),
        SignatureMethod.hexlify(txData ?? '0x'),
        SignatureMethod.hexlify(chainId),
        '0x',
        '0x'
      ];
      final rlpEnCodeTransaction = SignatureMethod.encode(tran2);
      // 转换为十六进制字符串
      // 转换为字节列表
      final hash = keccak256(hexToBytes(rlpEnCodeTransaction));
      Map<String, dynamic> data = {
        "tran": tran2,
        'tx': bytesToHex(hash),
      };
      await web3.dispose();
      return data;
    } catch (e) {
      await web3.dispose();
      throw Exception(e);
    }
  }

  // 获取 以太 gas value为 int 类型 ，
  static Future<Map<String, dynamic>?> getErc20Gas(rpcUrl, transaction) async {
    final client = Web3Client(rpcUrl, Client());
    try {
      var gasPrice = await client.getGasPrice();
      String gasPriceStr = gasPrice.getInWei.toString();
      BigInt gas = BigInt.from(0);
      if (transaction.length > 0) {
        Map<String, dynamic> tran = jsonDecode(transaction);
        if (tran["gasPrice"] != null && tran["gasPrice"].toString().isNotEmpty) {
          gasPriceStr = tran["gasPrice"].toString();
        }
        if (tran["gas"] != null && tran["gas"].toString().isNotEmpty) {
          gas = BigInt.from(int.parse(tran["gas"].toString()));
        } else {
          final EthereumAddress sender = EthereumAddress.fromHex(tran['from']);
          final EthereumAddress to = EthereumAddress.fromHex(tran['to']);
          if ((tran['data'] == null || tran['data'].toString().isEmpty)) {
            try {
              gas = await client.estimateGas(
                sender: sender,
                to: to,
                value: EtherAmount.fromInt(EtherUnit.wei, int.parse(tran['value'])),
                gasPrice: gasPrice,
              );
            } catch (e) {
              gas = await client.estimateGas();
            }
          } else {
            try {
              gas = await client.estimateGas(
                  sender: sender,
                  to: to,
                  value: EtherAmount.fromInt(EtherUnit.wei, 0),
                  gasPrice: gasPrice,
                  data: hexToBytes(tran['data'])
                  // EtherAmount? value,
                  );
            } catch (e) {
              gas = await client.estimateGas();
            }
          }
        }
      } else {
        // final data = tran.data?
        gas = await client.estimateGas(
          to: EthereumAddress.fromHex('0x0000000000000000000000000000000000000000'),
          gasPrice: gasPrice,
        );
      }
      final adjustedGas = (gas * BigInt.from(1.1)).toString();
      await client.dispose();
      return {'gasPrice': gasPriceStr, 'gas': gas.toString(), "adjustedGas": adjustedGas};
    } catch (e) {
      await client.dispose();
      return throw Exception(e.toString());
    }
  }

  // 获取 币种信息  chainId, contractAddress, rpc
  static Future<Map<String, dynamic>?> getERC20TokenInfoByRpc(String rpcUrl, String tokenAddress) async {
    // String rpcUrl = params['rpcUrl'];
    // String tokenAddress = params['tokenAddress'];
    final client = Web3Client(rpcUrl, Client());
    try {
      String erc20Abi = await rootBundle.loadString('assets/abi/erc20.abi.json');
      final contractAddress = EthereumAddress.fromHex(tokenAddress);
      //-------------------------  币种名称
      final contractNameABI = ContractAbi.fromJson(erc20Abi, 'name');
      final contractName = DeployedContract(contractNameABI, contractAddress);
      // 币种 符号
      final contractSymbolABI = ContractAbi.fromJson(erc20Abi, 'symbol');
      final contractSymbolName = DeployedContract(contractSymbolABI, contractAddress);
      // 币种 符号
      final contractDecimalsABI = ContractAbi.fromJson(erc20Abi, 'Decimals');
      final contractDecimalsName = DeployedContract(contractDecimalsABI, contractAddress);
      List<dynamic> result = await Future.wait([
        client.call(
          contract: contractName,
          function: contractName.function('name'),
          params: [],
        ),
        client.call(
          contract: contractSymbolName,
          function: contractSymbolName.function('symbol'),
          params: [],
        ),
        client.call(
          contract: contractDecimalsName,
          function: contractDecimalsName.function('decimals'),
          params: [],
        ),
        client.getChainId()
      ]);
      String name = result[0][0];
      String symbol = result[1][0];
      String decimals = (result[2][0]).toString();
      String chainId = result[3].toString();
      print('getERC20TokenInfoByRpc: $result');
      await client.dispose();
      return {
        "chainId": int.parse(chainId),
        "address": contractAddress.toString(),
        "name": name.toString(),
        "decimals": int.parse(decimals),
        "symbol": symbol.toString(),
      };
    } catch (e) {
      print('e $e');
      await client.dispose();
      return null;
    }
  }
}
